#include "rwlock.h"

int _linda_create_rw_lock(char* path, _linda_rw_lock* lock)
{
  // TODO: error checking
  int fd;
  char zero[_N_PARAM];
  
  fd = open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
  
  memset(zero, 0, _N_PARAM);
  
  write(fd, zero, _N_PARAM);
  
  lseek(fd, 0, SEEK_SET); // na wszelki wypadek
  
  lock->shared_vars = (int*) mmap(NULL, _N_PARAM, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  lock->fd = fd;
  lock->path = strdup(path);
  
  lock->our_lock = _LOCK_NONE;
  
  lock->master_mutex = _linda_sem_init(ftok(path, 2), 1);
  lock->writer_mutex = _linda_sem_init(ftok(path, 3), 0);
  lock->reader_mutex = _linda_sem_init(ftok(path, 4), 0);
  lock->reader_queue_mutex = _linda_sem_init(ftok(path, 5), 0);
  lock->switch_mutex = _linda_sem_init(ftok(path, 6), 0);
  
  /*lock->writer_count = 0;
  lock->reader_count = 0;
  lock->waiting_writer_count = 0;
  lock->waiting_reader_count = 0;

  lock->switched_section = 0;*/
  return 0;
}

void _linda_destroy_rw_lock(_linda_rw_lock* lock)
{ 
  _linda_sem_destroy(lock->master_mutex);
  _linda_sem_destroy(lock->writer_mutex);
  _linda_sem_destroy(lock->reader_mutex);
  _linda_sem_destroy(lock->reader_queue_mutex);
  _linda_sem_destroy(lock->switch_mutex);
  
  munmap(lock->shared_vars, _N_PARAM);
  close(lock->fd);
  unlink(lock->path);
  free(lock->path);
}

int _linda_get_rw_lock(char* path, _linda_rw_lock* lock)
{
  int fd;
  
  fd = open(path, O_RDWR);
  lock->shared_vars = (int*) mmap(NULL, _N_PARAM, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  lock->fd = fd;
  lock->path = strdup(path);
  
  lock->our_lock = _LOCK_NONE;
  
  lock->master_mutex = _linda_sem_get(ftok(path, 2));
  lock->writer_mutex = _linda_sem_get(ftok(path, 3));
  lock->reader_mutex = _linda_sem_get(ftok(path, 4));
  lock->reader_queue_mutex = _linda_sem_get(ftok(path, 5));
  lock->switch_mutex = _linda_sem_get(ftok(path, 6));  
  
  return 0;
}

void _linda_leave_rw_lock(_linda_rw_lock* lock)
{
  _linda_sem_destroy(lock->master_mutex);
  _linda_sem_destroy(lock->writer_mutex);
  _linda_sem_destroy(lock->reader_mutex);
  _linda_sem_destroy(lock->reader_queue_mutex);
  _linda_sem_destroy(lock->switch_mutex);
  
  munmap(lock->shared_vars, _N_PARAM);
  close(lock->fd);
  free(lock->path);  
}

int _linda_create_rw_condition(_linda_rw_lock* lock, char* path,_linda_rw_condition* cond)
{
  cond->lock = lock;
  cond->wait_semaphore = _linda_sem_init(ftok(path, 1), 0);
  
  return 0;
}

void _linda_destroy_rw_condition(_linda_rw_condition* cond)
{
  _linda_sem_destroy(cond->wait_semaphore);
}

int _linda_get_rw_condition(_linda_rw_lock* lock, char* path,_linda_rw_condition* cond)
{
  cond->lock = lock;
  cond->wait_semaphore = _linda_sem_get(ftok(path, 1));
  
  return 0;
}

void _linda_lock_reader(_linda_rw_lock* lock)
{
  int i;
  
  _linda_sem_P(lock->master_mutex, 0);
  lock->our_lock = _LOCK_READER;
  if (lock->reader_count != 0)
  {
    // Otwarte
  } else if (lock->writer_count != 0) {
    if (lock->waiting_reader_count == 0)
    {
      // Pierwsi w kolejce; my budzmy wszystkich
      lock->waiting_reader_count++;
      _linda_sem_V(lock->master_mutex); // przelaczanie sekcji
      _linda_sem_P(lock->reader_mutex, 0); // a la monitor
      // Zostalismy obudzeni
      for (i = 0;i < lock->waiting_reader_count-1;i++) _linda_sem_V(lock->reader_queue_mutex);
      lock->waiting_reader_count = 0;
    } else {
      // Ktos juz byl
      lock->waiting_reader_count++;
      _linda_sem_V(lock->master_mutex);
      _linda_sem_P(lock->reader_queue_mutex, 0);
      // Ktos nas obudzil
    }
  } else {
     // Nikogo nie bylo     
  }
  lock->reader_count++;
  _linda_sem_V(lock->master_mutex); 
}

void _linda_release_reader(_linda_rw_lock* lock)
{
  _linda_sem_P(lock->master_mutex, 0);
  lock->our_lock = _LOCK_NONE;
  lock->reader_count--;
  if (lock->switched_section)
  {
    // Sekcja zostala przelaczona; oddaj
    lock->switched_section = 0;
    _linda_sem_V(lock->switch_mutex);
  } else if (lock->reader_count > 0) {
    // Sa jeszcze czytelnicy - nie oddawaj blokady
    _linda_sem_V(lock->master_mutex);
  } else if (lock->waiting_writer_count != 0) {
    // Czeka pisarz - wpusc
    _linda_sem_V(lock->writer_mutex);
  } else {
    // Jezeli nikt nie czeka oddajemy sekcje
    _linda_sem_V(lock->master_mutex);
  }
}

void _linda_lock_writer(_linda_rw_lock* lock)
{
  _linda_sem_P(lock->master_mutex, 0);
  lock->our_lock = _LOCK_WRITER;
  if ((lock->writer_count != 0) || (lock->reader_count != 0))
  {
    // Ktos siedzi
    lock->waiting_writer_count++;
    _linda_sem_V(lock->master_mutex); // przelaczanie sekcji
    _linda_sem_P(lock->writer_mutex, 0); // a la monitor
    // ...tu czekamy...
    lock->waiting_writer_count--;
  }
  lock->writer_count = 1;
  _linda_sem_V(lock->master_mutex);
}

void _linda_release_writer(_linda_rw_lock* lock)
{
  _linda_sem_P(lock->master_mutex, 0);
  lock->our_lock = _LOCK_NONE;
  lock->writer_count--;
  if (lock->switched_section)
  {
    // Sekcja zostala przelaczona; oddaj
    lock->switched_section = 0;
    _linda_sem_V(lock->switch_mutex);
    _linda_sem_V(lock->master_mutex);
  } else if (lock->waiting_reader_count != 0) {
    // Czeka jakis czytelnik - warto go wpuscic (a on wpusci kolejnych)
    _linda_sem_V(lock->reader_mutex);
  } else if (lock->waiting_writer_count != 0) {
    // Jesli nie ma czytaczy to mozna wpusisc pisarza
    _linda_sem_V(lock->writer_mutex);
  } else {
    // Jezeli nikt nie czeka oddajemy sekcje
    _linda_sem_V(lock->master_mutex);
  }
}

void _linda_writer_wait_condition(_linda_rw_condition* cond, int timeout)
{
  int x;
  // "Normalnie" oddajemy blokade
  if (cond->lock->our_lock == _LOCK_WRITER) _linda_release_writer(cond->lock);
    else printf("ERR: wait() wywolany bez blokady pisarza! Czytaj plik rwlock.h\n");
  x = _linda_sem_P(cond->wait_semaphore, timeout);
  if (x == 0)
  {
    // Zostalismy obudzeni
    cond->lock->writer_count++;
    cond->lock->our_lock = _LOCK_WRITER;
  } else if (x == 1) {
    // Timeout - musimy recznie ubiegac sie o sekcje pisarza
    _linda_lock_writer(cond->lock);
  }
}

void _linda_writer_signal_condition(_linda_rw_condition* cond)
{
  int mutex = cond->lock->switch_mutex;
  
  if (cond->lock->our_lock != _LOCK_WRITER)
    printf("ERR: signal() wywolany bez blokady pisarza! Czytaj plik rwlock.h\n");
  // Przelacz sekcje
  cond->lock->switched_section = 1;
  _linda_sem_V(cond->wait_semaphore);
  _linda_sem_P(mutex, 0);
}